﻿using System;
using System.Configuration;
using System.Net;
using System.Net.Configuration;
using System.Net.Mail;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using Company.Merchandising.BusinessLayer.DataEntities;
using Company.Merchandising.DataAccessLayer.DataAccessCommon;
using Company.Merchandising.ServiceLayer.EntityManagement.Credentials.Enums;

namespace Company.Merchandising.ServiceLayer.EntityManagement.Credentials
{
	public class CredentialsManager : BaseManager
	{
		private static CredentialsManagerSection _managerSection;
		private const string _hashAlgoritm = "SHA512";
		private const int _loginMaximumLength = 64;
		private const int _passwordMaximumLength = 256;
		private DateTime _minSqlDateTime = new DateTime(1970, 1, 1);

		static CredentialsManager()
		{
			_managerSection = (CredentialsManagerSection)ConfigurationManager.GetSection("credentialsGroup/credentialsManager");
			_managerSection.ValidateSection();
		}

		public CredentialsManager(MerchModelEntities modelEntities) : base(modelEntities) { }

		public CredentialValidateStatus ValidateCredential(string credentialLogin, string credentialPassword)
		{
			if (credentialLogin == null)
			{
				throw new ArgumentNullException("credentialLogin");
			}

			if (credentialPassword == null)
			{
				throw new ArgumentNullException("credentialPassword");
			}

			DACCredential dacCredential = new DACCredential(_modelEntities);
			var credential = dacCredential.GetCredentialByLoginName(credentialLogin);
			if (credential == null)
			{
				return CredentialValidateStatus.InvalidLoginOrPassword;
			}

			DateTime now = DateTime.Now;
			credential.LastLoginAttempt = now;
			if (!credential.IsActive)
			{
				_modelEntities.SaveChanges();
				return CredentialValidateStatus.CredentialIsNotActive;
			}

			if (!credential.IsLocked)
			{
				if ((now - credential.LastLoginAttempt.Value).Minutes < _managerSection.LockAccountTime)
				{
					_modelEntities.SaveChanges();
					return CredentialValidateStatus.CredentialsIsLocked;
				}
				else
				{
					credential.IsLocked = false;
					credential.FailedLoginAttempts = _managerSection.LoginAttempts;
				}
			}

			if (!CheckPassword(credentialPassword, credential.Password, credential.PasswordSalt))
			{
				credential.FailedLoginAttempts--;
				if (credential.FailedLoginAttempts > 0)
				{
					_modelEntities.SaveChanges();
					return CredentialValidateStatus.InvalidLoginOrPassword;
				}
				else
				{
					credential.IsLocked = true;
					_modelEntities.SaveChanges();
					return CredentialValidateStatus.InvalidLoginOrPassword;
				}
			}

			credential.LastLogin = DateTime.Now;
			if ((now - credential.LastPasswordChange).Days > _managerSection.PasswordChangePeriod)
			{
				_modelEntities.SaveChanges();
				return CredentialValidateStatus.NeedToChangePassword;
			}

			_modelEntities.SaveChanges();
			return CredentialValidateStatus.Ok;
		}

		public CredentialCreateStatus CreateCredential(string credentialLogin, string credentialPassword)
		{
			if (credentialLogin == null)
			{
				throw new ArgumentNullException("credentialLogin");
			}

			if (credentialPassword == null)
			{
				throw new ArgumentNullException("credentialPassword");
			}

			if (!Regex.IsMatch(credentialLogin, _managerSection.LoginRegex))
			{
				return CredentialCreateStatus.LoginDoesntMeetPolicyRequirements;
			}

			if (!Regex.IsMatch(credentialPassword, _managerSection.PasswordRegex))
			{
				return CredentialCreateStatus.PasswordDoesntMeetPolicyRequirements;
			}

			if (credentialLogin.Length > _loginMaximumLength)
			{
				return CredentialCreateStatus.TooLongLogin;
			}

			string salt = GenerateSalt();
			string computedPasswordHash = EncodePassword(credentialPassword, salt);
			if (computedPasswordHash.Length > _passwordMaximumLength)
			{
				return CredentialCreateStatus.TooLongPassword;
			}

			DACCredential dacCredential = new DACCredential(_modelEntities);
			var credential = dacCredential.GetCredentialByLoginName(credentialLogin);
			if (credential != null)
			{
				return CredentialCreateStatus.LoginAlreadyExists;
			}

			Credential newCredential = new Credential();
			newCredential.Login = credentialLogin;
			newCredential.Password = computedPasswordHash;
			newCredential.PasswordSalt = salt;
			newCredential.FailedLoginAttempts = _managerSection.LoginAttempts;
			newCredential.LastPasswordChange = _minSqlDateTime;
			newCredential.IsActive = false;

			_modelEntities.AddObject(newCredential.GetType().Name, newCredential);
			_modelEntities.SaveChanges();

			return CredentialCreateStatus.Ok;
		}

		public CredentialDeleteStatus DeleteCredential(string credentialLogin)
		{
			if (credentialLogin == null)
			{
				throw new ArgumentNullException("credentialLogin");
			}

			DACCredential dacCredential = new DACCredential(_modelEntities);
			var credential = dacCredential.GetCredentialByLoginName(credentialLogin);
			if (credential == null)
			{
				return CredentialDeleteStatus.CredentialDeletedOrDoesNotExist;
			}

			credential.IsDeleted = true;
			_modelEntities.SaveChanges();
			return CredentialDeleteStatus.Ok;
		}

		public CredentialChangePasswordStatus ChangePassword(string credentialLogin, string credentialOldPassword, string credentialNewPassword, string credentialNewRepeatedPassword)
		{
			if (credentialLogin == null)
			{
				throw new ArgumentNullException("credentialLogin");
			}

			if (credentialOldPassword == null)
			{
				throw new ArgumentNullException("credentialOldPassword");
			}

			if (credentialNewPassword == null)
			{
				throw new ArgumentNullException("credentialNewPassword");
			}

			if (credentialNewRepeatedPassword == null)
			{
				throw new ArgumentNullException("credentialNewRepeatedPassword");
			}

			if (credentialNewPassword != credentialNewRepeatedPassword)
			{
				return CredentialChangePasswordStatus.PasswordsDoNotMatchs;
			}

			DACCredential dacCredential = new DACCredential(_modelEntities);
			var credential = dacCredential.GetCredentialByLoginName(credentialLogin);
			if (credential == null)
			{
				return CredentialChangePasswordStatus.CredentialDoesNotExist;
			}

			if (!CheckPassword(credentialOldPassword, credential.Password, credential.PasswordSalt))
			{
				return CredentialChangePasswordStatus.InCorrectOldPassword;
			}

			if (credentialOldPassword == credentialNewPassword)
			{
				return CredentialChangePasswordStatus.NewPasswordIsTheSame;
			}

			credential.LastPasswordChange = DateTime.Now;
			_modelEntities.SaveChanges();

			return CredentialChangePasswordStatus.Ok;
		}

		public CredentialResetPasswordStatus ResetPassword(string credentialLogin)
		{
			if (credentialLogin == null)
			{
				throw new ArgumentNullException("credentialLogin");
			}

			var dacCredential = new DACCredential(_modelEntities);
			var credential = dacCredential.GetCredentialByLoginName(credentialLogin);
			if (credential == null)
			{
				return CredentialResetPasswordStatus.CredentialDoesNotExist;
			}

			if (credential.Employee == null)
			{
				return CredentialResetPasswordStatus.CredentialDoesNotHavePerson;
			}

			SmtpSection smtpSection = (SmtpSection)ConfigurationManager.GetSection("system.net/mailSettings/smtp");
			string randomPassword = GenerateRandomPassword();
			MailMessage mail = new MailMessage();
			mail.To.Add(credential.Employee.Email);
			mail.From = new MailAddress(smtpSection.From);
			mail.Subject = "Resetowanie hasła";
			mail.Body = string.Format("Nowe tymczasowe hasło: {0}.", randomPassword);

			SmtpClient smtpClient = new SmtpClient();
			smtpClient.Host = smtpSection.Network.Host;
			if (!string.IsNullOrEmpty(smtpSection.Network.UserName))
			{
				smtpClient.Credentials = new NetworkCredential(smtpSection.Network.UserName, smtpSection.Network.Password);
			}
			else
			{
				smtpClient.UseDefaultCredentials = true;
			}

			try
			{
				smtpClient.Send(mail);
			}
			catch
			{
				return CredentialResetPasswordStatus.CanNotSendMail;
			}

			credential.LastPasswordChange = _minSqlDateTime;
			credential.Password = EncodePassword(randomPassword, GenerateSalt());
			_modelEntities.SaveChanges();
			return CredentialResetPasswordStatus.Ok;
		}

		private static string GenerateRandomPassword()
		{
			const string allowedChars = "abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ0123456789";
			const int passwordLength = 10;
			Random randNum = new Random();
			char[] chars = new char[passwordLength];
			int allowedCharCount = allowedChars.Length;

			for (int i = 0; i < passwordLength; i++)
			{
				chars[i] = allowedChars[(int)((allowedChars.Length) * randNum.NextDouble())];
			}

			return new string(chars);
		}

		private static string GenerateSalt()
		{
			byte[] buffor = new byte[16];
			(new RNGCryptoServiceProvider()).GetBytes(buffor);
			return Convert.ToBase64String(buffor);
		}

		private static string EncodePassword(string password, string salt)
		{
			byte[] passwordBytes = Encoding.Unicode.GetBytes(password);
			byte[] saltBytes = Convert.FromBase64String(salt);
			byte[] passwordWithSaltBytes = new byte[passwordBytes.Length + saltBytes.Length];

			Buffer.BlockCopy(saltBytes, 0, passwordWithSaltBytes, 0, saltBytes.Length);
			Buffer.BlockCopy(passwordBytes, 0, passwordWithSaltBytes, saltBytes.Length, passwordBytes.Length);

			HashAlgorithm algoritm = HashAlgorithm.Create(_hashAlgoritm);
			return Convert.ToBase64String(algoritm.ComputeHash(passwordWithSaltBytes));
		}

		private static bool CheckPassword(string password, string passwordHash, string salt)
		{
			string computedHash = EncodePassword(password, salt);
			return computedHash == passwordHash;
		}
	}
}
